#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <netdb.h>


#define ADDRESS "224.0.50.112"
#define SERVICE "5050"
#define MAX_BUF_SIZE 65535

typedef struct _header{
	uint8_t version; /* must be 1. If you receive anything else, discard*/
	uint8_t ttl; /* must be 1. If you receive anything else, discard*/
	uint16_t payload_length; /* bytes following the header */
	uint32_t account_identifier; /* digits of your account name */
	uint32_t source_address; /* unused for now, set to 0 and ignore. */
	uint32_t destination_address; /* unused for now, set to 0 and ignore. */
	uint16_t checksum; /* unused for now, set to 0 and ignore. */
	uint16_t protocol; /* must be 1. If you receive anything else, discard*/
} header;

int my_error(char *message){
	//fprintf(stderr, "error: %s\n", message);
	exit(1);
}

int make_header_str(char *dest, int pay_len){
	header h;
	
	if(pay_len > MAX_BUF_SIZE - 28){
		my_error("message too long");
	}
	
	h.version = 1;
	h.ttl = 1;
	h.payload_length = htons(pay_len);
	h.account_identifier = htonl(417130);
	h.source_address = htonl(0);
	h.destination_address = htonl(0);
	h.checksum = htons(0);
	h.protocol = htons(1);
	
	memcpy(dest, &h, sizeof(h));
	
	
	return 1;
}

int check_header_str(char *src){
	header h;
	
	memcpy(&h, src, sizeof(h));
	
	if(h.version != 1){
		printf("version = %d error\n", h.version);
		return 0;
	}
	if(h.ttl != 1){
		printf("ttl = %d error\n", h.ttl);
		return 0;
	}
	if(ntohs(h.payload_length) > (MAX_BUF_SIZE - 28)){
		printf("payload_length = %d error\n", ntohs(h.payload_length));
		return 0;
	}
	if(ntohs(h.protocol) != 1){
		printf("protocol = %d error\n", ntohs(h.protocol));
		return 0;
	}
	
	return ntohs(h.payload_length);
}



int sendMessageToGroup(char *msg, char *service){
	char buf[MAX_BUF_SIZE];
	int status, sendSock, bytesSent, buflen;
	struct addrinfo addrCriteria;
	struct addrinfo *multicastAddr;
	u_char mcTTL = (u_char) 1;
	
	if(!(make_header_str(buf, strlen(msg)))){
		my_error("make_header_str() failed");
	}
	
	strncpy(buf + 20, msg, strlen(msg));
	
	buflen = 20 + strlen(msg);
	buf[buflen] = '\0';
	//printf("from within sMTG(), message: %s\n", (buf + 20));
	
	
	memset(&addrCriteria, 0, sizeof(addrCriteria));
	addrCriteria.ai_family = AF_INET;
	addrCriteria.ai_socktype = SOCK_DGRAM;
	addrCriteria.ai_protocol = IPPROTO_UDP;
	addrCriteria.ai_flags |= AI_NUMERICHOST;
	
	
	status = getaddrinfo(ADDRESS, SERVICE, &addrCriteria, &multicastAddr);
	if(status != 0){
		my_error("getaddrinfo() failed");
	}
	
	sendSock = socket(multicastAddr->ai_family, multicastAddr->ai_socktype, multicastAddr->ai_protocol);
	//should be identical to socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if(sendSock < 0){
		my_error("socket() failed");
	}
	
	
	/*
		This is the section to be worked.  The rest works fine... I think.
		Just need to open up a socket for sending, and then send the message in buf (header and payload already properly formed).
	*/
	
	if(setsockopt(sendSock, IPPROTO_IP, IP_MULTICAST_TTL, &mcTTL, sizeof(mcTTL)) < 0){
		my_error("setsockopt(IP_MULTICAST_TTL) failed");
	}
	
	bytesSent = sendto(sendSock, buf, buflen, 0, multicastAddr->ai_addr, multicastAddr->ai_addrlen);
	if(bytesSent < 0){
		my_error("sendto() failed");
	} else if(bytesSent != buflen){
		my_error("sendto() didn't send right number of bytes");
	} 
	
	freeaddrinfo(multicastAddr);
	close(sendSock);
	return 1;
}




int main(int argc, char *argv[]){
	char recvmsg[MAX_BUF_SIZE];
	char sendmsg[MAX_BUF_SIZE] = "hello";
	char port[20] = "14327";
	struct addrinfo addrCriteria;
	struct addrinfo *multicastAddr;
	struct sockaddr fromAddr;
	int x, y, z, a, b, c, i, recvSock, sendSock, recvmsgLen;
	struct ip_mreq joinRequest;
	z = 0;
	a = 1;
	switch(argc){
		case 1:
			break;
		case 2:
			strcpy(sendmsg, argv[1]);
			break;
		default:
			my_error("Parameter(s): [<message>]");
			break;
	}
	
	//printf("message: %s\n", sendmsg);
	
	memset(&addrCriteria, 0, sizeof(addrCriteria));
	addrCriteria.ai_family = AF_INET;
	addrCriteria.ai_socktype = SOCK_DGRAM;
	addrCriteria.ai_protocol = IPPROTO_UDP;
	addrCriteria.ai_flags |= AI_NUMERICHOST;
	
	x = getaddrinfo(ADDRESS, SERVICE, &addrCriteria, &multicastAddr);
	if(x != 0){
		my_error("getaddrinfo() failed");
	}
	
	recvSock = socket(multicastAddr->ai_family, multicastAddr->ai_socktype, multicastAddr->ai_protocol);
	//should be identical to socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if(recvSock < 0){
		my_error("socket() failed");
	}
	
	if(setsockopt(recvSock, SOL_SOCKET, SO_REUSEADDR, &a, sizeof(a)) < 0){
		my_error("setsockopt(SOL_SOCKET->SO_REUSEADDR) failed");
	}
	
	if(bind(recvSock, multicastAddr->ai_addr, multicastAddr->ai_addrlen) < 0){
		
		z = 1;
		for(c = 14327; c < 20000; c++){
			sprintf(port, "%d", c);
			
			
			x = getaddrinfo(ADDRESS, port, &addrCriteria, &multicastAddr);
			if(x != 0){
				my_error("getaddrinfo() failed");
			}
			
			recvSock = socket(multicastAddr->ai_family, multicastAddr->ai_socktype, multicastAddr->ai_protocol);
			if(recvSock < 0){
				my_error("socket() failed");
			}
			b = bind(recvSock, multicastAddr->ai_addr, multicastAddr->ai_addrlen);
			if(b >= 0) break;
		}
		if(c == 20000){
			my_error("bind() failed");
		} else {
			printf("port: %d\n", c);
		}
	}
	/*
		Now, join the multicast group
	*/
	
	joinRequest.imr_multiaddr = ((struct sockaddr_in *) multicastAddr->ai_addr)->sin_addr;
	joinRequest.imr_interface.s_addr = 0;
	
	if(setsockopt(recvSock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &joinRequest, sizeof(joinRequest)) < 0){
		my_error("setsockopt(IPPROTO_IP->IP_ADD_MEMBERSHIP) failed");
	}
	
	
	
	sendMessageToGroup(sendmsg, (z? port : SERVICE));
	
	
	
	
	
	
	/*
		receive multicasted messages ad infinitum
	*/
	
	while(42){
		//printf("gonna receive:\n");
		recvmsgLen = recvfrom(recvSock, recvmsg, MAX_BUF_SIZE, 0, NULL, 0);
		if(recvmsgLen < 0){
			my_error("recvfrom() failed");
		}
		
		y = check_header_str(recvmsg);
		/*
		if(y != (recvmsgLen - 20)){
			my_error("invalid header or wrong length");
		}
		*/
		recvmsg[20 + y] = '\0';
		
		for(i = 20; i < 20 + y; i++){
			putchar(recvmsg[i]);
		}
		printf("\n");
		//printf("%s\n", recvmsg + 20);
	}	
	
	
	
	//printf("\n***\nmade it to cleanup\n");
	freeaddrinfo(multicastAddr);
	close(recvSock);
	return 1;
}














